package com.hmdp.service.impl;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.hmdp.dto.Result;
import com.hmdp.entity.RedisData;
import com.hmdp.entity.Shop;
import com.hmdp.mapper.ShopMapper;
import com.hmdp.service.IShopService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sun.org.apache.regexp.internal.RE;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static com.hmdp.utils.RedisConstants.*;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public Result queryShopById(Long id) {
        //
        //Shop shop = QueryWithPassThough(id);
        //Shop  shop = QueryMutiLocks(id);
        Shop shop = QueryTimeOver(id);
        if(shop == null){
            return Result.fail("查询店铺不存在");
        }
        return Result.ok(shop);
    }
    //获得锁
    public boolean tryLock(String lockName){
        Boolean flag =
                redisTemplate.opsForValue().setIfAbsent(lockName, "1", LOCK_TTL, SECONDS);
        return BooleanUtil.isTrue(flag);
    }
    //释放锁
    public void unlock(String lockName){
        redisTemplate.delete(lockName);
    }
    //互斥锁
    public Shop QueryMutiLocks(Long id){
        String key = CACHE_SHOP + id;

            //先查询redis
            String shopInfo = redisTemplate.opsForValue().get(key);
            //缓存中有内容
            if (StrUtil.isNotBlank(shopInfo)) {
                Shop shop = JSONUtil.toBean(shopInfo, Shop.class);
                return shop;
            }
            if (shopInfo != null) {
                return null;
            }
            Shop shop = new Shop();
            //实现缓存重建
            String lockName = CACHE_LOCK_SHOP + key;
        try {
            //获得互斥锁
            boolean flag = tryLock(lockName);
            //判断是否成功
            if (!flag) {
                //休眠50毫秒
                Thread.sleep(50);
                //递归直到拿到锁
                return QueryWithPassThough(id);
            }
            //缓存没有，则查询数据库
            shop = getById(id);
            //模拟重建延迟，高并发
            //Thread.sleep(200);
            if (shop == null) {
                //将空值缓存到redis
                redisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, MINUTES);
                return null;
            }
            //将数据保存到缓存
            redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, MINUTES);
            //如果有将数据反序列化为Shop对象,则返回json字符串
            //释放互斥锁
        }catch(Exception e) {
            throw  new RuntimeException(e);
        }finally {
            unlock(lockName);
        }
        return shop;
    }
    private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(10);
    //逻辑过期解决缓存穿透
    public Shop QueryTimeOver(Long id){
        String key = CACHE_SHOP + id;

        //先查询redis
        String shopInfo = redisTemplate.opsForValue().get(key);
        //缓存中有内容，直接返回
        if (StrUtil.isBlank(shopInfo)){
            return null;
        }
        //命中缓存，做反序列化为对象
        RedisData redisData = JSONUtil.toBean(shopInfo, RedisData.class);
        Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
        //判断是否过期
        LocalDateTime expireTime = redisData.getExpireTime();
        if(expireTime.isAfter(LocalDateTime.now())){
            //未过期直接返回shop
            return shop;
        }
        //过期，获得锁
        String lock = CACHE_LOCK_SHOP + id;
        boolean isLock = tryLock(lock);
        if(isLock){
            //获取锁成功，开启独立线程，线程池
            EXECUTOR_SERVICE.submit(()->{
                try {
                    this.saveShopToRedis(id,20L);
                }catch (Exception e){
                    throw  new RuntimeException(e);
                }finally {
                    //释放锁
                    unlock(lock);
                }


            });
        }
        //未过期直接返回
        return shop;
    }

    //缓存击穿
    public Shop QueryWithPassThough(Long id){
        String key = CACHE_SHOP + id;
        //先查询redis
        String shopInfo = redisTemplate.opsForValue().get(key);
        //缓存中有内容
        if(StrUtil.isNotBlank(shopInfo)){
            Shop shop = JSONUtil.toBean(shopInfo, Shop.class);
            return null;
        }
        if(shopInfo != null){
            return null;
        }
        //缓存没有，则查询数据库
        Shop shop = getById(id);
        if(shop == null){
            //将空值缓存到redis
            redisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,MINUTES);
            return null;
        }
        //将数据保存到缓存
        redisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL,MINUTES);
        //如果有将数据反序列化为Shop对象,则返回json字符串
        return shop;
    }

    @Override
    public Result update(Shop shop) {

        if(shop.getId()==null){
            return Result.fail("店铺id为空");
        }
        //1.更新数据库
        updateById(shop);
        //删除缓存
        redisTemplate.delete(CACHE_SHOP+shop.getId());
        return Result.ok();
    }

    public void saveShopToRedis(Long id,Long seconds){
        //查询数据库
        Shop shop = getById(id);
        RedisData redisData = new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(seconds));
        redisTemplate.opsForValue().set(CACHE_SHOP+id,JSONUtil.toJsonStr(redisData));
    }

}
